/*
 * StringRenderer.cpp
 *
 *  Created on: 2011/08/07
 *      Author: naoki
 */

/**
 * @file Font hasn't been set.
 */
#include "StdAfx.h"
#include "NTEngine/Scene/StringRenderer.h"
#include "NTEngine/Scene/Font.h"
#include "NTEngine/Math/Vector3.h"
#include "NTEngine/Math/Vector2.h"
#include "NTEngine/Math/Rectangle.h"
#include "NTEngine/Base/Stack.h"
#include "NTEngine/Base/Array.h"
//#include "NTEngine/Scene/SpriteRenderer.h"
#include "NTEngine/Scene/PrimitiveRenderer.h"
#include "NTEngine/Graphics/Manager.h"
#include "NTEngine/Graphics/Texture.h"
#include "NTEngine/Base/ReferenceCounter.h"
#include "NTEngine/Base/DebugStream.h"
#include <string>
#include <algorithm>
using namespace std;

namespace NTEngine {
using namespace Graphics;
using namespace Math;
namespace Scene {

//ソート用便利クラス
template< class T > struct PointerLess {
	bool operator()( const T* a, const T* b ) {
		return ( *a < *b );
	}
};

/**
 * @brief
 */
class StringRenderer::Impl : public ReferenceCounter {
public:
	Impl( int capacity, int request )
	: mCharCapacity( capacity )
	, mRequests( request ) {
		//m_Sprite = SpriteRenderer::create( capacity );
		mPrimitiveRenderer = PrimitiveRenderer::create( mCharCapacity * 2, mCharCapacity + 4 );
		mPrevFrameId = Manager().frameId() - 1;
	}
	~Impl() {
	}

	void add( const Vector2& p, const char* s, unsigned c, bool wrap, float z ) {
		HARD_ASSERT( mCurrentFont && "Font hasn't been set." );
		mRequests.push();
		Request* r = mRequests.get();
		r->position = p;
		r->depth = z;
		r->str = s;
		r->color = c;
		r->font = mCurrentFont;
		r->wrap = wrap;
	}

	void draw() {
		//フレームIDチェック
		unsigned fid = Graphics::Manager().frameId();
		HARD_ASSERT( ( mPrevFrameId != fid ) && "StringRenderer::draw() : you can't draw() twice in a frame!" );
		mPrevFrameId = fid;

		if ( mRequests.size() == 0 ) { //やることない
			return;
		}
		//TriangleRendererに標準ステートを発行
		mPrimitiveRenderer.setBlendMode( Graphics::BLEND_LINEAR );
		mPrimitiveRenderer.setCullMode( Graphics::CULL_NONE );
		mPrimitiveRenderer.enableDepthTest( false );
		mPrimitiveRenderer.enableDepthWrite( false );
		//現在のビューポートを取得してfloat化
		Vector2 screenSize;
		int tw, th;
		Graphics::Manager().getViewport( 0, 0, &tw, &th );
		screenSize.set(
			static_cast< float >( tw ),
			static_cast< float >( th ) );

		//文字バッファを用意
		Array< Char > chars( mCharCapacity );
		int charPosition = 0;
		//リクエストを取り出して処理。
		while ( mRequests.size() > 0 ){
			Request& r = *mRequests.get();
			Vector2 pos = r.position;
			float charHeight = static_cast< float >( r.font.charHeight() );
			//一文字づつ取り出す。unsigned charにする必要あり
			int n = static_cast< int >( r.str.size() );
			const unsigned char* s = reinterpret_cast< const unsigned char* >( r.str.c_str() );
			for ( int i = 0; i < n; ++i ){
				//Char準備
				int code = s[ i ];
				//ShiftJis範囲か？
				if ( ( code >= 0x81 && code <= 0x9F ) || ( code >= 0xE0 && code <= 0xFC ) ){
					if ( ( i + 1 < n ) ){
						code <<= 8;
						code |= s[ i + 1 ]; //2バイトコード
						++i;
					}
				}
				if ( code == '\n' ){ //改行文字ですな。
					pos.x = r.position.x;
					pos.y += charHeight;
					continue;
				}
				//文字ゲット
				Char& c = chars[ charPosition ];
				bool found = r.font.getChar(
					&c.texture,
					&c.uv0,
					&c.uv1,
					code );
				if ( found ){ //文字あった
					//現在位置にピクセルを加えていく。
					c.size.setSub( c.uv1, c.uv0 );
					//テクスチャから幅と高さをもらって、ピクセルに直す比を求める。
					c.size.x *= static_cast< float >( c.texture.width() );
					c.size.y *= static_cast< float >( c.texture.height() );
				}else{ //文字なかった。豆腐を出す
					c.size.set( charHeight, charHeight ); //正方形
				}
				//カリングと折り返し計算
				//折り返しアリの時はちょっとでもはみ出すなら折り返す。
				if ( r.wrap ){
					if ( pos.x + c.size.x > screenSize.x ){
						pos.x = r.position.x;
						pos.y += charHeight;
					}
				}else{ //折り返しなしなら完全にあふれた時に切り捨てる
					if ( pos.x > screenSize.x ){
						break;
					}
				}
				//縦が完全にあふれた場合のみ終了。
				if ( pos.y > screenSize.y ){
					break;
				}
				//エントリ追加
				c.color = r.color;
				c.position.set( pos.x, pos.y, r.depth );
				++charPosition;
				//文字送り
				pos.x += c.size.x;
			}
			mRequests.pop();
		}
		if ( charPosition == 0 ){ //文字がない
			return;
		}
		//処理後文字バッファをZとテクスチャでソート
		Array< Char* > charPointers( charPosition );
		for ( int i = 0; i < charPosition; ++i ){
			charPointers[ i ] = &chars[ i ];
		}
		sort( &charPointers[ 0 ], &charPointers[ 0 ] + charPosition, PointerLess< Char >() );
		//ソートされた順に文字バッファをTriangleBufferにadd
		Texture lastTexture;
		for ( int i = 0; i < charPosition; ++i ){
			Char& c = *charPointers[ i ];
			//テクスチャが前と違えばセット
			if ( lastTexture != c.texture ){
				mPrimitiveRenderer.setTexture( c.texture );
				lastTexture = c.texture;
			}
			//頂点を作って四角形を描画
			Vector2 p[ 2 ];
			p[ 0 ].set( c.position.x, c.position.y ); //左上点
			p[ 1 ].set( c.position.x + c.size.x, c.position.y + c.size.y ); //右下
			//描画
			if ( c.texture ){ //テクスチャあれば
				mPrimitiveRenderer.addRectangle(
					p[ 0 ], p[ 1 ],
					c.uv0, c.uv1,
					c.color );
			}else{ //ねえよ。豆腐出したれ
				mPrimitiveRenderer.addRectangle(
					p[ 0 ], p[ 1 ],
					c.color );
			}
		}
		//全部終わったのでTriangleRendererを描画
		mPrimitiveRenderer.draw();
		/*
		Vector2 tmp( 200, 200 );
		Rectangle t_rect( 16, 0, 16, 16 );
		for ( int i = 0; i < charPosition; ++i ) {
			Char* c = &chars[ i ];
			Vector2 p( c->position.x, c->position.y );
			Rectangle rect(
				static_cast<int>( c->uv0.x ),
				static_cast<int>( c->uv0.y ),
				static_cast<int>( c->uv1.x ),
				static_cast<int>( c->uv1.y )
			);
			Vector2 scale( 1.F, 1.F );
			mPrimitiveRenderer.add( c->texture, p, rect, col, scale, c->position.z );
		}
		*/
	}

	void setFont( Font& font ) {
		mCurrentFont = font;
	}

private:
	struct Request {
		Vector2 position;
		float depth;
		string str;
		unsigned color;
		Font font;
		bool wrap;
	};
	struct Char {
		bool operator<( const Char& a ) const {
			if ( position.z > a.position.z ) {
				return true;
			} else if ( position.z < a.position.z ) {
				return false;
			} else if ( texture < a.texture ) {
				return true;
			} else {
				return false;
			}
		}
		Vector3 position;
		Vector2 uv0;
		Vector2 uv1;
		Vector2 size;
		Texture texture;
		unsigned color;
	};
	int mCharCapacity;
	Stack< Request > mRequests;
	Font mCurrentFont;
	unsigned mPrevFrameId;
	PrimitiveRenderer mPrimitiveRenderer;
};

StringRenderer StringRenderer::create( int capacity, int request ) {
	StringRenderer ret;
	ret.mImpl = NEW Impl( capacity, request );
	return ret;
}

void StringRenderer::add( int x, int y, const char* s, unsigned int c, bool wrap, float z ) {
	ASSERT( mImpl );
	mImpl->add( Math::Vector2( x, y ), s, c, wrap, z );
}

void StringRenderer::add( const Vector2& p, const char* s, unsigned int c, bool wrap, float z ) {
	ASSERT( mImpl );
	mImpl->add( p, s, c, wrap, z );
}

void StringRenderer::draw() {
	ASSERT( mImpl );
	mImpl->draw();
}

void StringRenderer::setFont( Font& font ) {
	ASSERT( mImpl );
	mImpl->setFont( font );
}

#define TYPE StringRenderer
#include "NTEngine/Base/Impl/ReferenceCounterTemplate.h"

} // namespace Scene
} // namespace NTEngine
// EOF
